#pragma once

#include "sfc/core/option.h"
#include "sfc/core/tuple.h"

namespace sfc::iter {

template <class Impl>
struct Iter : Impl {
  using Item = typename Impl::Item;

 public:
  auto nth(usize n) -> option::Option<Item> {
    for (auto i = 0U; i < n; ++i) {
      this->next();
    }
    return this->next();
  }

  void for_each(auto&& f) {
    while (auto x = this->next()) {
      f(x.get_unchecked_mut());
    }
  }

  template <class B>
  auto fold(B init, auto&& f) -> B {
    auto accum = init;
    while (auto x = this->next()) {
      accum = f(accum, x.get_unchecked_mut());
    }
    return accum;
  }

  auto reduce(auto&& f) -> option::Option<Item> {
    auto item = this->next();
    if (!item) {
      return {};
    }

    auto ptr = &item.get_unchecked_mut();
    while (auto x = this->next()) {
      ptr = &f(*ptr, x.get_unchecked_mut());
    }
    return *ptr;
  }

  auto find(auto&& pred) -> option::Option<Item> {
    while (auto x = this->next()) {
      if (pred(x.get_unchecked_mut())) {
        return x;
      }
    }
    return {};
  }

  auto rfind(auto&& pred) -> option::Option<Item> {
    while (auto x = this->next_back()) {
      if (pred(x.get_unchecked_mut())) {
        return x;
      }
    }
    return {};
  }

  auto position(auto&& pred) -> option::Option<usize> {
    for (auto idx = 0UL; auto x = this->next(); ++idx) {
      if (pred(x.get_unchecked_mut())) {
        return idx;
      }
    }
    return {};
  }

  auto rposition(auto&& pred) -> option::Option<usize> {
    for (auto idx = this->len() - 1; auto x = this->next_back(); --idx) {
      if (pred(x.get_unchecked_mut())) {
        return idx;
      }
    }
    return {};
  }

  auto all(auto&& f) -> bool {
    while (auto x = this->next()) {
      if (!f(x.get_unchecked_mut())) {
        return false;
      }
    }
    return true;
  }

  auto any(auto&& f) -> bool {
    while (auto x = this->next()) {
      if (f(x.get_unchecked_mut())) {
        return true;
      }
    }
    return false;
  }

  auto count(auto&& f) -> usize {
    auto accum = 0UL;
    this->for_each([&](auto& x) mutable { f(x) ? accum += 1 : void(); });
    return accum;
  }

  auto min() -> option::Option<Item> {
    return this->reduce([](auto&& a, auto&& b) -> decltype(auto) { return a < b ? a : b; });
  }

  auto max() -> option::Option<Item> {
    return this->reduce([](auto&& a, auto&& b) -> decltype(auto) { return a > b ? a : b; });
  }

  auto min_by_key(auto&& f) -> option::Option<Item> {
    return this->reduce([&](auto&& a, auto&& b) -> decltype(auto) { return f(a) < f(b) ? a : b; });
  }

  auto max_by_key(auto&& f) -> option::Option<Item> {
    return this->reduce([&](auto&& a, auto&& b) -> decltype(auto) { return f(a) > f(b) ? a : b; });
  }

  template <class S>
  auto sum() -> S {
    return this->fold(S{0}, [](auto& a, auto& b) { return S{a + b}; });
  }

  template <class S>
  auto product() -> S {
    return this->fold(S{1}, [](auto& a, auto& b) { return S{a * b}; });
  }

  auto rev();

  template <class F>
  auto map(F func);

  template <class P>
  auto filter(P pred);
};

}  // namespace sfc::iter
